/*
 *  Copyright 2004 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.webapp.taglib.core.grid;

import com.blandware.atleap.webapp.taglib.core.util.TaglibConstants;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.JspFragment;
import java.io.IOException;
import java.util.Iterator;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * <p>Tag for iterating over rows of grid. Each row represents a piece of data.
 * <br />
 * This tag is only valid when nested within <em>grid</em> tag
 * </p>
 * <p>
 * Allowed attributes are:
 * <ul>
 * <li>
 * <b>beanId</b> - identifier of Spring bean to use for getting rows for this
 * grid
 * </li>
 * <li>
 * <b>method</b> - method of specified bean to call to get collection for
 * iterating. This method must return instance of
 * {@link com.blandware.atleap.common.util.PartialCollection} and
 * accept instance of {@link com.blandware.atleap.common.util.QueryInfo}
 * as a single argument.
 * </li>
 * <li>
 * <b>collection</b> - collection of objects to display if <b>beanId</b> or <b>method</b> not
 * specified. Only instances of <cody>java.util.Collection</code>,
 * </code>java.lang.Object[]</code> and <code>java.util.Map</code> are
 * supported.
 * </li>
 * <li>
 * <b>tagId</b> - identifier for this iterator tag. It must be unique within
 * one grid.
 * </li>
 * <li>
 * <b>var</b> - required - name of page-scoped variable to save result of each
 * iteration under
 * </li>
 * <li>
 * <b>oddClass</b> - CSS class to export when odd row is processed
 * </li>
 * <li>
 * <b>evenClass</b> - CSS class to export when even row is processed
 * </li>
 * <li>
 * <b>firstRowEven</b> - indicates which CSS class to use for first row.
 * If value is <code>Boolean.TRUE</code> (it is by default) or attribute is
 * omitted, first row class will be that which is specified in <b>evenClass</b>
 * attribute.<br />
 * Note, that if grid has more than one iterator and this one is not the
 * first, value of this attribute is ignored: evennes will be determined
 * according to last row number in grid, it depends on iterators which have
 * already finished their work.
 * </li>
 * <li>
 * <b>cssVar</b> - name of page scope variable to save CSS class for current
 * row. If not specified, but any of <b>oddClass</b> or <b>evenClass</b> is
 * specified, this will be <code>TaglibConstants.ROW_CLASS_ATTR</code>.
 * </li>
 * <li>
 * <b>indexId</b> - name of page scope variable to save index of each iteration.
 * If not specified, this will be <code>TaglibConstants.INDEX_ID_ATTR</code>.
 * </li>
 * </ul>
 * </p>
 * <p>
 * Either <b>collection</b> or both <b>beanId</b> and <b>method</b> must be specified.
 * If <b>beanId</b> and <b>method</b> are specified, collection, over which
 * iteration will be done, will be obtained invoking <b>method</b> on bean,
 * which ID is <b>beanId</b>. Else, <b>collection</b> will be used as such
 * collection.
 * </p>
 * <p>
 * On each iteration, following is done: current row is obtained and exported
 * to page scope variable which name is specified by <b>var</b>. Then evenness
 * of current row is computed and corresponding style class is exported to
 * page scope variable which name is specified by <b>cssVar</b>. After that,
 * index of current iteration is exported to page scope variable which name is
 * specified by <b>indexId</b> and, at last, tag body is rendered.
 * </p>
 * <p><a href="RowsIteratorTag.java.html"><i>View Source</i></a></p>
 *
 * @author Sergey Zubtsovskiy <a href="mailto:sergey.zubtsovskiy@blandware.com">&lt;sergey.zubtsovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.23 $ $Date: 2007/03/29 10:37:43 $
 * @jsp.tag name="rowsIterator"
 * body-content="scriptless"
 * @see com.blandware.atleap.webapp.taglib.core.grid.GridTag
 */
public class RowsIteratorTag extends FetchRowsSupport {

    protected transient final Log log = LogFactory.getLog(RowsIteratorTag.class);

    /**
	 * Page context key to check the oddness of first row for this iterator.
	 */
	public static final String IS_ODD_ROW_KEY = "com.blandware.atleap.webapp.taglib.core.grid.IS_ODD_ROW";

    /**
	 * Name of page-scoped variable to save result of each iteration under
	 */
	protected String var;

	/**
	 * CSS class to export when odd row is processed
	 */
	protected String oddClass = new String();

	/**
	 * CSS class to export when even row is processed
	 */
	protected String evenClass = new String();

	/**
	 * Indicates which CSS class to use for first row.
	 * If value is <code>Boolean.TRUE</code> (it is by default) or attribute is omitted, first row class will be that which is specified
	 * in <code>evenClass</code> attribute.
	 * Note, if grid has more than one iterator and this one is not the first, value of this attribute is ignored:
	 * evennes will be determined according to last row number in grid, it depends on iterators which have already
	 * finished their work.
	 */
	protected Boolean firstRowEven = Boolean.TRUE;

	/**
	 * Name of page scope variable to save CSS class under
	 */
	protected String cssVar;

	/**
	 * Index of each iteration will be saved in page context variable with
     * this name
	 */
	protected String indexId;

	/**
	 * Iterator of the elements of this collection, while we are actually
	 * running.
	 */
	private Iterator rowsIterator = null;

	/**
	 * Flag to check what CSS class must be returned
	 */
	private boolean isOddRow = true;


	/**
	 * Contains number of current iteration
	 */
	private int loopCount = 0;

    /**
     * Returns name of variable which will accept result of each iteration
     *
	 * @return variable name
     * @see #var
	 * @jsp.attribute required="true"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Name of page scope variable to save result of each iteration under"
	 */
	public String getVar() {
		return var;
	}

    /**
     * Sets name of variable which will accept result of each iteration
     *
     * @param var variable name to set
     * @see #var
     */
	public void setVar(String var) {
		this.var = var;
	}

	/**
     * Returns CSS class for odd rows
     *
	 * @return odd rows class
     * @see #oddClass
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="CSS class to export when odd row is processed"
	 */
	public String getOddClass() {
		return oddClass;
	}

    /**
     * Sets CSS class for odd rows
     *
     * @param oddClass odd rows class to set
     * @see #oddClass
     */
	public void setOddClass(String oddClass) {
		this.oddClass = oddClass;
	}

	/**
     * Returns CSS class for even rows
     *
	 * @return even rows class
     * @see #evenClass
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="CSS class to export when even row is processed"
	 */
	public String getEvenClass() {
		return evenClass;
	}

    /**
     * Sets CSS class for even rows
     *
     * @param evenClass even rows class
     * @see #evenClass
     */
	public void setEvenClass(String evenClass) {
		this.evenClass = evenClass;
	}

	/**
     * Returns whether first row is even
     *
	 * @return whether first row is even
     * @see #firstRowEven
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.Boolean"
	 * description="Indicates which CSS class to use for first row"
	 */
	public Boolean getFirstRowEven() {
		return firstRowEven;
	}

    /**
     * Sets whether first row is even
     *
     * @param firstRowEven whether first row is even
     * @see #firstRowEven
     */
	public void setFirstRowEven(Boolean firstRowEven) {
		this.firstRowEven = firstRowEven;
	}

	/**
     * Returns name of variable that will accept CSS style for current row
     *
	 * @return CSS variable name
     * @see #cssVar
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Name of page scope variable to save CSS class under"
	 */
	public String getCssVar() {
		return cssVar;
	}

    /**
     * Sets name of variable that will accept CSS style for current row
     *
     * @param cssVar CSS variable name to set
     * @see #cssVar
     */
	public void setCssVar(String cssVar) {
		this.cssVar = cssVar;
	}

	/**
     * Returns name of variable that will accept index of current iteration
     *
	 * @return index variable name
     * @see #indexId
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="If specified, index of each iteration will be saved in corresponding page context"
	 */
	public String getIndexId() {
		return indexId;
	}

    /**
     * Sets name of variable that will accept index of current iteration
     *
     * @param indexId index variable name to set
     * @see #indexId
     */
	public void setIndexId(String indexId) {
		this.indexId = indexId;
	}

	/**
	 * Constructs an iterator for collection of rows and loops through the body
     * once per element.
	 *
	 * @throws JspTagException if a JSP exception has occurred
	 * @throws IOException     if there is an error when invoking body content
	 */
	public void doTag() throws JspException, IOException {

		PageContext pageContext = (PageContext) getJspContext();

        GridTag parentGridTag = getParentGridTag();

        rowsIterator = fetchCollection(parentGridTag, pageContext, true).iterator();

        if ( cssVar == null && ((oddClass != null && oddClass.trim().length() != 0) || (evenClass != null && evenClass.trim().length() != 0)) ) {
            cssVar = TaglibConstants.ROW_CLASS_ATTR;
        }

        if ( indexId == null ) {
            indexId = TaglibConstants.INDEX_ID_ATTR;
        }

		if ( firstRowEven == null ) {
			firstRowEven = Boolean.TRUE;
		}

		if ( pageContext.getAttribute(IS_ODD_ROW_KEY) != null ) {
			isOddRow = ((Boolean) pageContext.getAttribute(IS_ODD_ROW_KEY)).booleanValue();
		} else {
			// determine evennes
			isOddRow = !firstRowEven.booleanValue();
		}

		while ( rowsIterator.hasNext() ) {
			// export next element to page context
			Object element = rowsIterator.next();
			if ( element == null ) {
				pageContext.removeAttribute(var);
			} else {
				pageContext.setAttribute(var, element);
			}

			if ( cssVar != null ) {
				String rowClass = isOddRow ? oddClass : evenClass;
				isOddRow = !isOddRow;
				pageContext.setAttribute(cssVar, rowClass);
			}

			pageContext.setAttribute(indexId, new Integer(loopCount++));

			// invoke body to standard output
			JspFragment body = getJspBody();
			if ( body != null ) {
				body.invoke(null);
			}
		}

		if ( loopCount > 0 ) {
			// save isOddRow variable value for another iterators, if they exist
			pageContext.setAttribute(IS_ODD_ROW_KEY, Boolean.valueOf(isOddRow));
		}
	}
}
